/***********************************************************************************************************************
*                                                                                                                      *
* SPLASH build system v0.1                                                                                             *
*                                                                                                                      *
* Copyright (c) 2013 Andrew D. Zonenberg                                                                               *
* All rights reserved.                                                                                                 *
*                                                                                                                      *
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the     *
* following conditions are met:                                                                                        *
*                                                                                                                      *
*    * Redistributions of source code must retain the above copyright notice, this list of conditions, and the         *
*      following disclaimer.                                                                                           *
*                                                                                                                      *
*    * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the       *
*      following disclaimer in the documentation and/or other materials provided with the distribution.                *
*                                                                                                                      *
*    * Neither the name of the author nor the names of any contributors may be used to endorse or promote products     *
*      derived from this software without specific prior written permission.                                           *
*                                                                                                                      *
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED   *
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL *
* THE AUTHORS BE HELD LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES        *
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR       *
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT *
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE       *
* POSSIBILITY OF SUCH DAMAGE.                                                                                          *
*                                                                                                                      *
***********************************************************************************************************************/

#include "splashcore.h"

//TODO: Figure out how to share this stuff properly and not duplicate

#define RPC_TYPE_CALL        0x0                   /* 3 bits wide */
#define RPC_TYPE_HOST_PROHIBITED 0x6               /* 3 bits wide */
#define RPC_TYPE_HOST_UNREACH 0x7                  /* 3 bits wide */
#define RPC_TYPE_INTERRUPT   0x4                   /* 3 bits wide */
#define RPC_TYPE_RETURN_FAIL 0x2                   /* 3 bits wide */
#define RPC_TYPE_RETURN_RETRY 0x3                  /* 3 bits wide */
#define RPC_TYPE_RETURN_SUCCESS 0x1                /* 3 bits wide */

using namespace std;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Construction / destruction

HDLNetlistNode::HDLNetlistNode(
		BuildGraph* graph, 
		std::vector<HDLSourceNode*> sources,
		std::string name,
		std::string top_level)
		: BuildGraphNode(graph)
		, m_sources(sources)
		, m_name(name)
		, m_topLevel(top_level)
{
}

HDLNetlistNode::~HDLNetlistNode()
{
	
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Dependency scanning

void HDLNetlistNode::UpdateDependencies()
{
	m_dependencies.clear();
	for(size_t i=0; i<m_sources.size(); i++)
	{
		HDLSourceNode* node = m_sources[i];
		string fname = node->GetOutputPath();
		m_dependencies.push_back(node);
		
		if(dynamic_cast<VerilogGeneratedSourceNode*>(node) != NULL)
			DebugPrintfSilent(3, "    File %s is auto-generated, adding but not parsing\n", fname.c_str());
		else if(dynamic_cast<VerilogSourceNode*>(node) != NULL)
			ScanVerilogSourceFile(fname);
		else
			FatalError("Don't know what to do with this node class");
	}
}

void HDLNetlistNode::ScanVerilogSourceFile(string fname)
{
	FILE* fp = fopen(fname.c_str(), "r");
	if(fp == NULL)
		FatalError("Could not open source file %s\n", fname.c_str());
	char line[1024];
	
	AntikernelModuleInfo* modinfo = NULL;
	AntikernelOpcodeInfo* opinfo = NULL;
	AntikernelPacketInfo* packinfo = NULL;

	//Read the file
	bool in_doxy_comment = false;
	while(NULL != (fgets(line, sizeof(line), fp)))
	{
		//Trim out leading spaces
		string sline(line);
		size_t pos = sline.find_first_not_of(" \t\n");
		if(pos != sline.npos)
			sline = sline.substr(pos);
		if(sline.empty())
			continue;
		
		//Process Doxygen comments
		if(in_doxy_comment)
		{
			if(sline.find("*/") != sline.npos)
			{
				in_doxy_comment = false;
				continue;
			}
			else
				ProcessDoxygenLine(fname, sline, modinfo, opinfo, packinfo);
		}
		
		//Look for Doxygen comments
		if(sline.find("/**") != sline.npos)
		{
			in_doxy_comment = true;
			continue;
		}
		
		/*
		//Look for attribute declarations
		if(sline.find("(*") != sline.npos)
		{
			char name[1024];
			char value[1024];
			if(2 != sscanf(sline.c_str(), "(* %1023s = \"%1023[^\"]\" *)", name, value))
				continue;
			
			//Ignore any attributes that aren't antikernel-related
			if(strstr(name, "ANTIKERNEL_") != name)
				continue;
				
			//If it's the opcode file, save it
			if(!strcmp(name, "ANTIKERNEL_RPC_OPCODE_FILE") )
			{
				if(value[0] != '/')
					rpc_opcode_file = CanonicalizePath(GetDirOfFile(fname) + "/" + value);
				else
					rpc_opcode_file = value;
			}
			
			else
				printf("    WARNING: Unknown Antikernel Verilog attribute %s = %s\n", name, value);
		}
		*/
		
		//Look for module declarations
		if(sline.find("module") != sline.npos)
		{
			char modname[1024];
			if(1 != sscanf(sline.c_str(), "module %1023[^( ]", modname))
				continue;
				
			if(modinfo != NULL)
			{
				modinfo->m_name = modname;
				m_graph->AddModuleInfo(modname, modinfo);
			}
			modinfo = NULL;
		}
			
		//See if it's a preprocessor directive
		if(sline[0] == '`')
		{
			//Ignore anything other than conditionals or includes
			if(sline.find("`timescale") != sline.npos)
				continue;
			else if(sline.find("`default_nettype") != sline.npos)
				continue;
			
			//For now, ignore conditionals
			else if(sline.find("`define") != sline.npos)
				continue;
			else if(sline.find("`ifdef") != sline.npos)
				continue;
			else if(sline.find("`ifndef") != sline.npos)
				continue;
			else if(sline.find("`else") != sline.npos)
				continue;
			else if(sline.find("`endif") != sline.npos)
				continue;
			else if(sline.find("`elsif") != sline.npos)
				continue;
			else if(sline.find("`elif") != sline.npos)
				continue;
				
			//Include
			else if(sline.find("`include") != sline.npos)
			{
				char include_fname[1024];
				if(1 != sscanf(sline.c_str(), "`include \"%1023[^\"]\"", include_fname))
					FatalError("Malformed include directive, cannot parse\n");
									
				DebugPrintfSilent(4, "    Found Verilog include directive: %s\n", include_fname);
				
				//List of search directories
				string searchpaths[]=
				{
					m_graph->GetOutDir() + "/generic/include",
					GetDirOfFile(fname)
				};
				
				//Search in order
				string include_path = "";
				for(size_t j=0; j<sizeof(searchpaths)/sizeof(searchpaths[0]); j++)
				{
					string path = searchpaths[j] + "/" + include_fname;
					DebugPrintfSilent(5, "    Trying path %s\n", path.c_str());
					
					//If it exists, we found it
					if(DoesFileExist(path))
					{
						include_path = path;
						break;
					}
					
					//If it does not exist, but is in the graph, it's automatically generated
					if(m_graph->GetNodeForPath(path, "generic") != NULL)
					{
						include_path = path;
						break;
					}
				}					
				if(include_path.empty())
					FatalError("Could not resolve Verilog include directive (path = \"%s\")\n", include_fname);
				DebugPrintfSilent(4, "    Resolved Verilog include directive: %s\n", include_path.c_str());
					
				//See if it exists already
				BuildGraphNode* node = m_graph->GetNodeForPath(include_path, "generic");
				if(node != NULL)
					m_dependencies.push_back(node);
				else
				{
					VerilogSourceNode* source = new VerilogSourceNode(m_graph, include_path);
					m_graph->AddNodeWithPath(source, include_path, "generic");
					m_dependencies.push_back(source);
				}
			}
			
			//Not implemented
			else 
			{
				//FatalError("Found unknown Verilog preprocessor directive (not implemented): %s\n", sline.c_str());
				DebugPrintfSilent(1, "    WARNING: Found unknown Verilog preprocessor directive (not implemented): %s", sline.c_str());
			}
		}
	}
	
	if(modinfo)
	{
		delete modinfo;
		modinfo = NULL;
	}
		
	fclose(fp);
}

void HDLNetlistNode::ProcessDoxygenLine(
	string fname,
	string sline,
	AntikernelModuleInfo*& modinfo,
	AntikernelOpcodeInfo*& opinfo,
	AntikernelPacketInfo*& packinfo)
{
	//Skip anything but @ keywords
	if(sline.find("@") == sline.npos)
		return;
		
	//Keyword line - parse it
	//If we get a @module, make a new module object.
	if(sline.find("@module") != sline.npos)
	{
		modinfo = new AntikernelModuleInfo;
		//m_graph->
		packinfo = NULL;
		opinfo = NULL;
	}
	
	//Brief description
	else if(sline.find("@brief") != sline.npos)
	{
		char bdesc[512];
		if(1 != sscanf(sline.c_str(), "@brief %511[^\n]", bdesc))
			return;
		if(packinfo)
			packinfo->m_desc = bdesc;
	}

	//Opcode file? Load it
	else if(sline.find("@opcodefile") != sline.npos)
	{
		char opcodefname[512];
		if(1 != sscanf(sline.c_str(), "@opcodefile %511[^\n]", opcodefname))
			return;
			
		//Get the absolute path to the opcode file
		string opcodefname_abs = opcodefname;
		if(opcodefname[0] != '/')
			opcodefname_abs = CanonicalizePath(GetDirOfFile(fname) + "/" + opcodefname);
			
		//We should already have a node for the opcode file
		ConstgenSourceNode* srcnode =
			dynamic_cast<ConstgenSourceNode*>(m_graph->GetNodeForPath(opcodefname_abs.c_str(), "generic"));
		if(srcnode == NULL)
		{
			//TODO
			//srcnode = new ConstgenSourceNode(graph, script);
			//graph->AddNodeWithPath(srcnode, script, "generic");
			FatalError("No opcode node exists for %s\n", opcodefname_abs.c_str());
		}
		
		//Now that we have the node, save it
		if(modinfo == NULL)
			FatalError("@opcodefile is not legal before a @module\n");
		modinfo->m_constants = srcnode->m_constants;
	}
	
	//RPC function, interrupt, etc
	else if(sline.find("@rpc") != sline.npos)
	{
		char funcname[512];
		char fntype[32];
		if(2 != sscanf(sline.c_str(), "@%31s %511s", fntype, funcname))
			return;
			
		//Verify that the opcode exists
		if( (modinfo == NULL) || (modinfo->m_constants.empty()) )
			FatalError("RPC function requires a @module and @opcodefile first\n");
		
		auto it = modinfo->m_constants.find(funcname);
		if(it == modinfo->m_constants.end())
			FatalError("Opcode \"%s\" is not in table", funcname);
			
		string sftype(fntype);
		int packtype;
		if(sftype == "rpcfn")
			packtype = RPC_TYPE_CALL;
		else if(sftype == "rpcfn_ok")
			packtype = RPC_TYPE_RETURN_SUCCESS;
		else if(sftype == "rpcfn_fail")
			packtype = RPC_TYPE_RETURN_FAIL;
		else if(sftype == "rpcint")
			packtype = RPC_TYPE_INTERRUPT;
		else
			FatalError("Unknown RPC type");
			
		//Look it up (implicitly creating if necessary) and add data
		unsigned int opval = it->second.second;
		opinfo = &modinfo->m_opcodeInfo[opval];
		opinfo->m_funcval = opval;
		packinfo = &opinfo->m_packetInfo[packtype];
		packinfo->m_opname = funcname;
	}
	
	//RPC function parameter
	else if(sline.find("@param") != sline.npos)
	{
		if(opinfo == NULL)
			return;
			
		char pname[512];
		char pval[512];
		char pdesc[512];
		if(3 != sscanf(sline.c_str(), "@param %511[^ \t] %511[^ \t] %511[^\n]", pname, pval, pdesc))
			return;
		
		//Valid formats:
		//d0[8:0]:dec
		//{d0[15:0], d1[31:0]}:mac
		
		//Skip curly brace if there is one
		unsigned int i = 0;
		if(pval[0] == '{')
			i++;
		
		AntikernelPacketVariableInfo varinfo;
		varinfo.m_displayName = pname;
		varinfo.m_desc = pdesc;

		//Expect one or more fields of the format dA[B:C] or dA[B]
		while( (i < sizeof(pval)) && (pval[i] != 0) )
		{
			//Decode this field
			if(GetNextNonWhitespace(pval, i) != 'd')
				FatalError("Syntax error: invalid RPC parameter format [1] (%s)\n", sline.c_str());
			unsigned int wordnum = ReadNumber(pval, i);
			if(GetNextNonWhitespace(pval, i) != '[')
				FatalError("Syntax error: invalid RPC parameter format [2] (%s)\n", sline.c_str());
			unsigned int bithi = ReadNumber(pval, i);
			unsigned int bitlo = bithi;
			if(GetNextNonWhitespace(pval, i, false) == ':')
			{
				i++;
				bitlo = ReadNumber(pval, i);
			}
			if(GetNextNonWhitespace(pval, i) != ']')
				FatalError("Syntax error: invalid RPC parameter format [3] (%s)\n", sline.c_str());
					
			varinfo.m_bitfields.push_back(AntikernelPacketBitfield(wordnum, bithi, bitlo));
			
			//Next character can be:
			//  ',' if another field coming
			//  '}' if last field
			//  ':' if only one field
			char delim = GetNextNonWhitespace(pval, i, false);
			if(delim == ',')
			{
				i++;					//skip, go on to next field
				continue;
			}
			else if(delim == '}')
			{
				i++;					//skip, expecting colon next
				break;
			}
			else if(delim == ':')		//stop but do not skip, next parsing stage expects a colon
				break;
		}
		
		//End of string?
		string format;
		if(pval[i] == 0)
			format = "hex";
		
		//Colon and format specifier
		else if(GetNextNonWhitespace(pval, i) == ':')
			format = pval + i;
		
		else
			FatalError("Syntax error: invalid RPC parameter format [4] (%s)\n", sline.c_str());
			
		//Parse format
		if(format == "dec")
			varinfo.m_displayType = AntikernelPacketVariableInfo::DISPLAY_TYPE_DEC;
		else if(format == "hex")
			varinfo.m_displayType = AntikernelPacketVariableInfo::DISPLAY_TYPE_HEX;
		else if(format == "nocaddr")
			varinfo.m_displayType = AntikernelPacketVariableInfo::DISPLAY_TYPE_NOCADDR;
		else if(format == "phyaddr")
			varinfo.m_displayType = AntikernelPacketVariableInfo::DISPLAY_TYPE_PHYADDR;
		else if(format == "mac")
			varinfo.m_displayType = AntikernelPacketVariableInfo::DISPLAY_TYPE_MACADDR;
		else if(format == "ipv6")
			varinfo.m_displayType = AntikernelPacketVariableInfo::DISPLAY_TYPE_IPV6;
		else if(format == "asciiz")
			varinfo.m_displayType = AntikernelPacketVariableInfo::DISPLAY_TYPE_ASCIIZ;
		else if(format == "enum")
			varinfo.m_displayType = AntikernelPacketVariableInfo::DISPLAY_TYPE_ENUM;
		else if(format == "fx8")
			varinfo.m_displayType = AntikernelPacketVariableInfo::DISPLAY_TYPE_FX8;
		else
			FatalError("Unknown display format \"%s\"\n", format.c_str());
			
		//If if's an enumerated value, parse the constants
		if(varinfo.m_displayType == AntikernelPacketVariableInfo::DISPLAY_TYPE_ENUM)
		{
			//TODO: separate field for filename?
			string sfn = pdesc;
			if(sfn[0] != '/')
				sfn = GetDirOfFile(fname) + "/" + pdesc;
			if(DoesFileExist(sfn))
			{
				//Get (and create if necessary) the parser node
				sfn = CanonicalizePath(sfn);
				ConstgenSourceNode* node = dynamic_cast<ConstgenSourceNode*>(m_graph->GetNodeForPath(sfn, "generic"));
				if(node == NULL)
				{
					node = new ConstgenSourceNode(m_graph, sfn);
					m_graph->AddNodeWithPath(node, sfn, "generic");
				}
				
				//Add the constants to the module database
				for(auto x : node->m_constants)
					varinfo.m_enumvals[x.second.second] = x.first;
			}
		}
			
		packinfo->m_varinfo.push_back(varinfo);
	}
	
	//Ignore anything else
	else
	{
		
	}
}

char HDLNetlistNode::GetNextNonWhitespace(const char* line, unsigned int& i, bool increment)
{
	EatSpaces(line, i);
	char ret = line[i];
	if(increment)
		i++;
	return ret;
}

void HDLNetlistNode::EatSpaces(const char* line, unsigned int& i)
{
	while(isspace(line[i]))
		i++;
}

unsigned int HDLNetlistNode::ReadNumber(const char* line, unsigned int& i)
{
	EatSpaces(line, i);
	
	unsigned int rval = 0;
	while(isdigit(line[i]))
		rval = (rval*10) + (line[i++] - '0');
	return rval;
}
